1 using System;
2 using System.Collections.Generic;
3 using UnityEngine;
4 using UnityEngine.Assertions;
5
6 namespace ProceduralToolkit.Examples
7 {
8 /// <summary>
9 /// Fully procedural building generator
10 /// </summary>
11 public class BuildingGenerator : BuildingGeneratorBase
12 {
13 private const float socleHeight = 1;
14 private const float floorHeight = 2.5f;
15 private const float atticHeight = 1;
16
17 private Dictionary<PanelType, List<Func<IFacadePanel>>> constructors =
18 new Dictionary<PanelType, List<Func<IFacadePanel>>>();
19 private Dictionary<PanelType, Func<IFacadePanel>> commonConstructors =
20 new Dictionary<PanelType, Func<IFacadePanel>>();
21
22 private Dictionary<PanelSize, float> sizeValues = new Dictionary<PanelSize, float>
23 {
24 {PanelSize.Narrow, 2.5f},
25 {PanelSize.Wide, 3},
26 };
27
28 public MeshDraft Generate(Config config)
29 {
30 Assert.IsTrue(config.width > 0);
31 Assert.IsTrue(config.length > 0);
32 Assert.IsTrue(config.floors > 0);
33 Assert.IsTrue(config.entranceInterval > 0);
34
35 InitializeConstructors(config.palette);
36
37 var foundationPolygon = new List<Vector2>
38 {
39 Vector2.left*config.length/2 + Vector2.down*config.width/2,
40 Vector2.right*config.length/2 + Vector2.down*config.width/2,
41 Vector2.right*config.length/2 + Vector2.up*config.width/2,
42 Vector2.left*config.length/2 + Vector2.up*config.width/2
43 };
44
45 commonConstructors[PanelType.Entrance] = constructors[PanelType.Entrance].GetRandom();
46 commonConstructors[PanelType.EntranceWindow] = constructors[PanelType.EntranceWindow].GetRandom();
47
48 var facadeLayouts = new List<FacadeLayout>();
49 for (int i = 0; i < foundationPolygon.Count; i++)
50 {
51 Vector2 a = foundationPolygon[i];
52 Vector2 b = foundationPolygon.GetLooped(i + 1);
53 float? entranceInterval = i == 0 ? config.entranceInterval : (float?) null;
54 bool hasBalconies = RandomE.Chance(0.5f);
55 facadeLayouts.Add(GenerateFacade(a, b, config.floors, hasBalconies, config.hasAttic, entranceInterval));
56 }
57
58 float facadeHeight = floorHeight*config.floors + socleHeight + (config.hasAttic ? atticHeight : 0);
59
60 var buildingDraft = GenerateFacades(foundationPolygon, facadeLayouts);
61 buildingDraft.uv.Clear();
62
63 var roof = RoofGenerator.Generate(foundationPolygon, facadeHeight, config.roofConfig);
64 roof.Paint(config.palette.roofColor);
65 buildingDraft.Add(roof);
66
67 return buildingDraft;
68 }
69
70 private void InitializeConstructors(Palette palette)
71 {
72 constructors[PanelType.Wall] = new List<Func<IFacadePanel>>
73 {
74 () => new ProceduralWall(palette.wallColor)
75 };
76 constructors[PanelType.Window] = new List<Func<IFacadePanel>>
77 {
78 () => new ProceduralWindow(palette.wallColor, palette.frameColor, palette.glassColor)
79 };
80 constructors[PanelType.Balcony] = new List<Func<IFacadePanel>>
81 {
82 () => new ProceduralBalcony(palette.wallColor, palette.frameColor, palette.glassColor),
83 () => new ProceduralBalconyGlazed(palette.wallColor, palette.frameColor, palette.glassColor,
84 palette.roofColor)
85 };
86 constructors[PanelType.Entrance] = new List<Func<IFacadePanel>>
87 {
88 () => new ProceduralEntrance(palette.wallColor, palette.doorColor),
89 () => new ProceduralEntranceRoofed(palette.wallColor, palette.doorColor, palette.roofColor)
90 };
91 constructors[PanelType.EntranceWindow] = new List<Func<IFacadePanel>>
92 {
93 () => new ProceduralEntranceWindow(palette.wallColor, palette.frameColor, palette.glassColor),
94 () => new ProceduralWindow(palette.wallColor, palette.frameColor, palette.glassColor)
95 };
96 constructors[PanelType.EntranceWallLast] = new List<Func<IFacadePanel>>
97 {
98 () => new ProceduralWall(palette.wallColor)
99 };
100 constructors[PanelType.Socle] = new List<Func<IFacadePanel>>
101 {
102 () => new ProceduralSocle(palette.socleColor),
103 () => new ProceduralSocleWindowed(palette.socleColor, palette.socleWindowColor)
104 };
105 constructors[PanelType.Attic] = new List<Func<IFacadePanel>>
106 {
107 () => new ProceduralAtticVented(palette.wallColor, palette.roofColor),
108 () => new ProceduralWall(palette.wallColor)
109 };
110 }
111
112 private FacadeLayout GenerateFacade(
113 Vector2 a,
114 Vector2 b,
115 int floors,
116 bool hasBalconies,
117 bool hasAttic,
118 float? entranceInterval)
119 {
120 float facadeWidth = (b - a).magnitude;
121 List<PanelSize> panelSizes = DivideFacade(sizeValues, facadeWidth);
122
123 var facadeLayout = GenerateFacadeChunk(facadeWidth, panelSizes, floors, hasBalconies, hasAttic,
124 entranceInterval);
125 facadeLayout.origin = Vector2.zero;
126 return facadeLayout;
127 }
128
129 private FacadeLayout GenerateFacadeChunk(
130 float facadeWidth,
131 List<PanelSize> panelSizes,
132 int floors,
133 bool hasBalconies,
134 bool hasAttic,
135 float? entranceInterval)
136 {
137 var facadeLayout = new VerticalLayout();
138 float facadeHeight = 0;
139
140 if (entranceInterval.HasValue)
141 {
142 int entranceCount = Mathf.Max(Mathf.FloorToInt(facadeWidth/entranceInterval.Value) - 1, 1);
143 int entranceIndexInterval = (panelSizes.Count - entranceCount)/(entranceCount + 1);
144
145 var horizontal = new HorizontalLayout();
146 int lastEntranceIndex = -1;
147 for (int i = 0; i < entranceCount; i++)
148 {
149 int entranceIndex = (i + 1)*entranceIndexInterval + i;
150
151 horizontal.Add(ConstructFacadeChunk(panelSizes, lastEntranceIndex + 1, entranceIndex, floors,
152 hasBalconies));
153
154 horizontal.Add(ConstructEntranceVertical(sizeValues[panelSizes[entranceIndex]], floors));
155
156 if (i == entranceCount - 1)
157 {
158 horizontal.Add(ConstructFacadeChunk(panelSizes, entranceIndex + 1, panelSizes.Count, floors,
159 hasBalconies));
160 }
161
162 lastEntranceIndex = entranceIndex;
163 }
164
165 facadeLayout.Add(horizontal);
166 facadeHeight += socleHeight + floors*floorHeight;
167 }
168 else
169 {
170 var socle = ConstructHorizontal(
171 constructors: constructors[PanelType.Socle],
172 height: socleHeight,
173 getPanelWidth: index => sizeValues[panelSizes[index]],
174 count: panelSizes.Count);
175
176 facadeLayout.Add(socle);
177 facadeHeight += socleHeight;
178
179 for (int floorIndex = 0; floorIndex < floors; floorIndex++)
180 {
181 HorizontalLayout floor;
182 if (floorIndex == 0)
183 {
184 floor = ConstructHorizontal(
185 constructors: constructors[PanelType.Window],
186 height: floorHeight,
187 getPanelWidth: index => sizeValues[panelSizes[index]],
188 count: panelSizes.Count);
189 }
190 else
191 {
192 floor = ConstructHorizontal(
193 constructors: hasBalconies
194 ? constructors[PanelType.Balcony]
195 : constructors[PanelType.Window],
196 height: floorHeight,
197 getPanelWidth: index => sizeValues[panelSizes[index]],
198 count: panelSizes.Count);
199 }
200
201 facadeLayout.Add(floor);
202 facadeHeight += floorHeight;
203 }
204 }
205
206 if (hasAttic)
207 {
208 var attic = ConstructHorizontal(
209 constructors: constructors[PanelType.Attic],
210 height: atticHeight,
211 getPanelWidth: index => sizeValues[panelSizes[index]],
212 count: panelSizes.Count);
213
214 facadeLayout.Add(attic);
215 facadeHeight += atticHeight;
216 }
217
218 facadeLayout.width = facadeWidth;
219 facadeLayout.height = facadeHeight;
220 return facadeLayout;
221 }
222
223 private VerticalLayout ConstructEntranceVertical(float width, int floors)
224 {
225 var vertical = ConstructVertical(
226 constructor: commonConstructors[PanelType.EntranceWindow],
227 width: width,
228 panelHeight: floorHeight,
229 count: floors - 1);
230
231 var entrance = commonConstructors[PanelType.Entrance]();
232 entrance.height = floorHeight;
233 vertical.Insert(0, entrance);
234
235 vertical.Add(constructors[PanelType.EntranceWallLast].GetRandom()());
236 return vertical;
237 }
238
239 private FacadeLayout ConstructFacadeChunk(List<PanelSize> panelSizes, int from, int to, int floors,
240 bool hasBalconies)
241 {
242 var sizes = panelSizes.GetRange(from, to - from);
243 float chunkWidth = 0;
244 foreach (var size in sizes)
245 {
246 chunkWidth += sizeValues[size];
247 }
248 return GenerateFacadeChunk(chunkWidth, sizes, floors, hasBalconies, false, null);
249 }
250
251 private static List<PanelSize> DivideFacade(Dictionary<PanelSize, float> sizeValues, float facadeWidth)
252 {
253 Dictionary<PanelSize, int> knapsack = PTUtils.Knapsack(sizeValues, facadeWidth);
254 var sizes = new List<PanelSize>();
255 foreach (var pair in knapsack)
256 {
257 for (var i = 0; i < pair.Value; i++)
258 {
259 sizes.Add(pair.Key);
260 }
261 }
262 sizes.Shuffle();
263 return sizes;
264 }
265
266 [Serializable]
267 public class Config
268 {
269 public float width = 12;
270 public float length = 36;
271 public int floors = 5;
272 public float entranceInterval = 12;
273 public bool hasAttic = true;
274 public RoofConfig roofConfig = new RoofConfig
275 {
276 type = RoofType.Flat,
277 thickness = 0.2f,
278 overhang = 0.2f,
279 };
280 public Palette palette = new Palette();
281 }
282
283 [Serializable]
284 public class Palette
285 {
286 public Color socleColor = ColorE.silver;
287 public Color socleWindowColor = (ColorE.silver/2).WithA(1);
288 public Color doorColor = (ColorE.silver/2).WithA(1);
289 public Color wallColor = ColorE.white;
290 public Color frameColor = ColorE.silver;
291 public Color glassColor = ColorE.white;
292 public Color roofColor = (ColorE.gray/4).WithA(1);
293 }
294
295 private enum PanelSize
296 {
297 Narrow,
298 Wide
299 }
300
301 private enum PanelType
302 {
303 Wall,
304 Window,
305 Balcony,
306 Entrance,
307 EntranceWindow,
308 EntranceWallLast,
309 Socle,
310 Attic,
311 };
312 }
313 }